home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / papyon / profile.py < prev    next >
Text File  |  2009-10-08  |  31KB  |  933 lines

  1. # -*- coding: utf-8 -*-
  2. #
  3. # papyon - a python client library for Msn
  4. #
  5. # Copyright (C) 2005-2006 Ali Sabil <ali.sabil@gmail.com>
  6. # Copyright (C) 2007-2008 Johann Prieur <johann.prieur@gmail.com>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21.  
  22. """Profile of the User connecting to the service, as well as the profile of
  23. contacts in his/her contact list.
  24.  
  25.     @sort: Profile, Contact, Group, ClientCapabilities
  26.     @group Enums: Presence, Membership, Privacy, NetworkID
  27.     @sort: Presence, Membership, Privacy, NetworkID"""
  28.  
  29. from papyon.util.decorator import rw_property
  30.  
  31. import gobject
  32.  
  33. __all__ = ['Profile', 'Contact', 'Group',
  34.         'Presence', 'Membership', 'ContactType', 'Privacy', 'NetworkID', 'ClientCapabilities']
  35.  
  36.  
  37. class ClientCapabilities(gobject.GObject):
  38.     """Capabilities of the client. This allow adverstising what the User Agent
  39.     is capable of, for example being able to receive video stream, and being
  40.     able to receive nudges...
  41.  
  42.         @ivar is_bot: is the client a bot
  43.         @type is_bot: bool
  44.  
  45.         @ivar is_mobile_device: is the client running on a mobile device
  46.         @type is_mobile_device: bool
  47.  
  48.         @ivar is_msn_mobile: is the client an MSN Mobile device
  49.         @type is_msn_mobile: bool
  50.  
  51.         @ivar is_msn_direct_device: is the client an MSN Direct device
  52.         @type is_msn_direct_device: bool
  53.  
  54.         @ivar is_media_center_user: is the client running on a Media Center
  55.         @type is_media_center_user: bool
  56.  
  57.         @ivar is_msn8_user: is the client using WLM 8
  58.         @type is_msn8_user: bool
  59.  
  60.         @ivar is_web_client: is the client web based
  61.         @type is_web_client: bool
  62.  
  63.         @ivar is_tgw_client: is the client a gateway
  64.         @type is_tgw_client: bool
  65.  
  66.         @ivar has_space: does the user has a space account
  67.         @type has_space: bool
  68.  
  69.         @ivar has_webcam: does the user has a webcam plugged in
  70.         @type has_webcam: bool
  71.  
  72.         @ivar has_onecare: does the user has the OneCare service
  73.         @type has_onecare: bool
  74.  
  75.         @ivar renders_gif: can the client render gif (for ink)
  76.         @type renders_gif: bool
  77.  
  78.         @ivar renders_isf: can the client render ISF (for ink)
  79.         @type renders_isf: bool
  80.  
  81.         @ivar supports_chunking: does the client supports chunking messages
  82.         @type supports_chunking: bool
  83.  
  84.         @ivar supports_direct_im: does the client supports direct IM
  85.         @type supports_direct_im: bool
  86.  
  87.         @ivar supports_winks: does the client supports Winks
  88.         @type supports_winks: bool
  89.  
  90.         @ivar supports_shared_search: does the client supports Shared Search
  91.         @type supports_shared_search: bool
  92.  
  93.         @ivar supports_voice_im: does the client supports voice clips
  94.         @type supports_voice_im: bool
  95.  
  96.         @ivar supports_secure_channel: does the client supports secure channels
  97.         @type supports_secure_channel: bool
  98.  
  99.         @ivar supports_sip_invite: does the client supports SIP
  100.         @type supports_sip_invite: bool
  101.  
  102.         @ivar supports_tunneled_sip: does the client supports tunneled SIP
  103.         @type supports_tunneled_sip: bool
  104.  
  105.         @ivar supports_shared_drive: does the client supports File sharing
  106.         @type supports_shared_drive: bool
  107.  
  108.         @ivar p2p_supports_turn: does the client supports TURN for p2p transfer
  109.         @type p2p_supports_turn: bool
  110.  
  111.         @ivar p2p_bootstrap_via_uun: is the client able to use and understand UUN commands
  112.         @type p2p_bootstrap_via_uun: bool
  113.  
  114.         @undocumented: __getattr__, __setattr__, __str__
  115.         """
  116.  
  117.     __gsignals__ =  {
  118.             "capability-changed": (gobject.SIGNAL_RUN_FIRST,
  119.                 gobject.TYPE_NONE,
  120.                 (object, object)),
  121.             }
  122.  
  123.     MSNC = [0x0,        # MSNC0
  124.             0x10000000, # MSNC1
  125.             0x20000000, # MSNC2
  126.             0x30000000, # MSNC3
  127.             0x40000000, # MSNC4
  128.             0x50000000, # MSNC5
  129.             0x60000000, # MSNC6
  130.             0x70000000, # MSNC7
  131.             0x80000000, # MSNC8
  132.             0x90000000, # MSNC9
  133.             0xA0000000] # MSNC10
  134.  
  135.     _CAPABILITIES = {
  136.             'is_bot': 0x00020000,
  137.             'is_mobile_device': 0x00000001,
  138.             'is_msn_mobile': 0x00000040,
  139.             'is_msn_direct_device': 0x00000080,
  140.  
  141.             'is_media_center_user': 0x00002000,
  142.             'is_msn8_user': 0x00000002,
  143.  
  144.             'is_web_client': 0x00000200,
  145.             'is_tgw_client': 0x00000800,
  146.  
  147.             'has_space': 0x00001000,
  148.             'has_webcam': 0x00000010,
  149.             'has_onecare': 0x01000000,
  150.  
  151.             'renders_gif': 0x00000004,
  152.             'renders_isf': 0x00000008,
  153.  
  154.             'supports_chunking': 0x00000020,
  155.             'supports_direct_im': 0x00004000,
  156.             'supports_winks': 0x00008000,
  157.             'supports_shared_search': 0x00010000,
  158.             'supports_voice_im': 0x00040000,
  159.             'supports_secure_channel': 0x00080000,
  160.             'supports_sip_invite': 0x00100000,
  161.             'supports_tunneled_sip': 0x00200000,
  162.             'supports_shared_drive': 0x00400000,
  163.  
  164.             'p2p_aware': 0xF0000000,
  165.             'p2p_supports_turn': 0x02000000,
  166.             'p2p_bootstrap_via_uun': 0x04000000
  167.             }
  168.  
  169.     _EXTRA = {
  170.             'supports_rtc_video': 0x00000010,
  171.             'unknown': 0x00000020
  172.             }
  173.  
  174.     def __init__(self, msnc=0, client_id="0:0"):
  175.         """Initializer
  176.  
  177.             @param msnc: The MSNC version
  178.             @type msnc: integer < 11 and >= 0
  179.  
  180.             @param client_id: the full client ID"""
  181.         gobject.GObject.__init__(self)
  182.         caps = client_id.split(":")
  183.         capabilities = int(caps[0])
  184.         if len(caps) > 1:
  185.             extra = int(caps[1])
  186.         else:
  187.             extra = 0
  188.         gobject.GObject.__setattr__(self, 'capabilities', self.MSNC[msnc] | capabilities)
  189.         gobject.GObject.__setattr__(self, 'extra', extra)
  190.  
  191.     def __getattr__(self, name):
  192.         if name in self._CAPABILITIES:
  193.             mask = self._CAPABILITIES[name]
  194.             id = self.capabilities
  195.         elif name in self._EXTRA:
  196.             mask = self._EXTRA[name]
  197.             id = self.extra
  198.         else:
  199.             raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name)
  200.         return (id & mask != 0)
  201.  
  202.     def __setattr__(self, name, value):
  203.         if name in self._CAPABILITIES:
  204.             mask = self._CAPABILITIES[name]
  205.             old_value = bool(self.capabilities & mask)
  206.             if value:
  207.                 gobject.GObject.__setattr__(self, 'capabilities', self.capabilities | mask)
  208.             else:
  209.                 gobject.GObject.__setattr__(self, 'capabilities', self.capabilities & ~mask)
  210.             if value != old_value:
  211.                 self.emit('capability-changed', name, value)
  212.         elif name in self._EXTRA:
  213.             mask = self._EXTRA[name]
  214.             old_value = bool(self.extra & mask)
  215.             if value:
  216.                 gobject.GObject.__setattr__(self, 'extra', self.extra | mask)
  217.             else:
  218.                 gobject.GObject.__setattr__(self, 'extra', self.extra & ~mask)
  219.             if value != old_value:
  220.                 self.emit('capability-changed', name, value)
  221.         else:
  222.             raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name)
  223.  
  224.     def __str__(self):
  225.         msnc = self.MSNC.index(self.capabilities & 0xF0000000)
  226.         if msnc >= 9:
  227.             client_id = "%s:%s" % (self.capabilities, self.extra)
  228.         else:
  229.             client_id = str(self.capabilities)
  230.         return client_id
  231.  
  232.  
  233. class NetworkID(object):
  234.     """Refers to the contact Network ID"""
  235.  
  236.     MSN = 1
  237.     """Microsoft Network"""
  238.  
  239.     LCS = 2
  240.     """Microsoft Live Communication Server"""
  241.  
  242.     MOBILE = 4
  243.     """Mobile phones"""
  244.  
  245.     EXTERNAL = 32
  246.     """External IM etwork, currently Yahoo!"""
  247.  
  248.  
  249. class Presence(object):
  250.     """Presence states.
  251.  
  252.     The members of this class are used to identify the Presence that a user
  253.     wants to advertise to the contacts on his/her contact list.
  254.  
  255.         @cvar ONLINE: online
  256.         @cvar BUSY: busy
  257.         @cvar IDLE: idle
  258.         @cvar AWAY: away
  259.         @cvar BE_RIGHT_BACK: be right back
  260.         @cvar ON_THE_PHONE: on the phone
  261.         @cvar OUT_TO_LUNCH: out to lunch
  262.         @cvar INVISIBLE: status hidden from contacts
  263.         @cvar OFFLINE: offline"""
  264.     ONLINE = 'NLN'
  265.     BUSY = 'BSY'
  266.     IDLE = 'IDL'
  267.     AWAY = 'AWY'
  268.     BE_RIGHT_BACK = 'BRB'
  269.     ON_THE_PHONE = 'PHN'
  270.     OUT_TO_LUNCH = 'LUN'
  271.     INVISIBLE = 'HDN'
  272.     OFFLINE = 'FLN'
  273.  
  274.  
  275. class Privacy(object):
  276.     """User privacy, defines the default policy concerning contacts not
  277.     belonging to the ALLOW list nor to the BLOCK list.
  278.  
  279.         @cvar ALLOW: allow by default
  280.         @cvar BLOCK: block by default"""
  281.     ALLOW = 'AL'
  282.     BLOCK = 'BL'
  283.  
  284.  
  285. class Membership(object):
  286.     """Contact Membership"""
  287.  
  288.     NONE = 0
  289.     """Contact doesn't belong to the contact list, but belongs to the address book"""
  290.  
  291.     FORWARD = 1
  292.     """Contact belongs to our contact list"""
  293.  
  294.     ALLOW   = 2
  295.     """Contact is explicitely allowed to see our presence regardless of the
  296.     currently set L{Privacy<papyon.profile.Privacy>}"""
  297.  
  298.     BLOCK   = 4
  299.     """Contact is explicitely forbidden from seeing our presence regardless of
  300.     the currently set L{Privacy<papyon.profile.Privacy>}"""
  301.  
  302.     REVERSE = 8
  303.     """We belong to the FORWARD list of the contact"""
  304.  
  305.     PENDING = 16
  306.     """Contact pending"""
  307.  
  308.  
  309. class ContactType(object):
  310.     """Automatic update status flag"""
  311.  
  312.     ME = "Me"
  313.     """Contact is the user so there's no automatic update relationship"""
  314.  
  315.     EXTERNAL = "Messenger2"
  316.     """Contact is part of an external messenger service so there's no automatic
  317.     update relationship with the user"""
  318.  
  319.     REGULAR = "Regular"
  320.     """Contact has no automatic update relationship with the user"""
  321.  
  322.     LIVE = "Live"
  323.     """Contact has an automatic update relationship with the user and an
  324.     automatic update already occured"""
  325.  
  326.     LIVE_PENDING = "LivePending"
  327.     """Contact was requested automatic update from the user and didn't
  328.     give its authorization yet"""
  329.  
  330.     LIVE_REJECTED = "LiveRejected"
  331.     """Contact was requested automatic update from the user and rejected
  332.     the request"""
  333.  
  334.     LIVE_DROPPED = "LiveDropped"
  335.     """Contact had an automatic update relationship with the user but
  336.     the contact dropped it"""
  337.  
  338.  
  339. class Profile(gobject.GObject):
  340.     """Profile of the User connecting to the service
  341.  
  342.         @undocumented: __gsignals__, __gproperties__, do_get_property"""
  343.  
  344.     __gproperties__ = {
  345.             "display-name": (gobject.TYPE_STRING,
  346.                 "Friendly name",
  347.                 "A nickname that the user chooses to display to others",
  348.                 "",
  349.                 gobject.PARAM_READABLE),
  350.  
  351.             "personal-message": (gobject.TYPE_STRING,
  352.                 "Personal message",
  353.                 "The personal message that the user wants to display",
  354.                 "",
  355.                 gobject.PARAM_READABLE),
  356.  
  357.             "current-media": (gobject.TYPE_PYOBJECT,
  358.                 "Current media",
  359.                 "The current media that the user wants to display",
  360.                 gobject.PARAM_READABLE),
  361.  
  362.             "signature-sound": (gobject.TYPE_PYOBJECT,
  363.                 "Signature sound",
  364.                 "The sound played by others' client when the user connects",
  365.                 gobject.PARAM_READABLE),
  366.  
  367.             "profile": (gobject.TYPE_PYOBJECT,
  368.                 "Profile",
  369.                 "the text/x-msmsgsprofile sent by the server",
  370.                 gobject.PARAM_READABLE),
  371.  
  372.             "presence": (gobject.TYPE_STRING,
  373.                 "Presence",
  374.                 "The presence to show to others",
  375.                 Presence.OFFLINE,
  376.                 gobject.PARAM_READABLE),
  377.  
  378.             "privacy": (gobject.TYPE_STRING,
  379.                 "Privacy",
  380.                 "The privacy policy to use",
  381.                 Privacy.BLOCK,
  382.                 gobject.PARAM_READABLE),
  383.  
  384.             "msn-object": (gobject.TYPE_STRING,
  385.                 "MSN Object",
  386.                 "MSN Object attached to the user, this generally represent "
  387.                 "its display picture",
  388.                 "",
  389.                 gobject.PARAM_READABLE),
  390.             }
  391.  
  392.     def __init__(self, account, ns_client):
  393.         gobject.GObject.__init__(self)
  394.         self._ns_client = ns_client
  395.         self._account = account[0]
  396.         self._password = account[1]
  397.  
  398.         self._profile = ""
  399.         self._display_name = self._account.split("@", 1)[0]
  400.         self._presence = Presence.OFFLINE
  401.         self._privacy = Privacy.BLOCK
  402.         self._personal_message = ""
  403.         self._current_media = None
  404.         self._signature_sound = None
  405.         self._end_point_name = ""
  406.  
  407.         self._client_id = ClientCapabilities(10)
  408.         self._client_id.supports_sip_invite = True
  409.         #self.client_id.supports_tunneled_sip = True
  410.         self._client_id.connect("capability-changed", self._client_capability_changed)
  411.  
  412.         self._msn_object = None
  413.  
  414.         self.__pending_set_presence = [self._presence, self._client_id, self._msn_object]
  415.         self.__pending_set_personal_message = [self._personal_message, self._current_media]
  416.  
  417.     @property
  418.     def account(self):
  419.         """The user account
  420.             @rtype: utf-8 encoded string"""
  421.         return self._account
  422.  
  423.     @property
  424.     def password(self):
  425.         """The user password
  426.             @rtype: utf-8 encoded string"""
  427.         return self._password
  428.  
  429.     @property
  430.     def profile(self):
  431.         """The user profile retrieved from the MSN servers
  432.             @rtype: dict of fields"""
  433.         return self._profile
  434.  
  435.     @property
  436.     def id(self):
  437.         """The user identifier in a GUID form
  438.             @rtype: GUID string"""
  439.         return "00000000-0000-0000-0000-000000000000"
  440.  
  441.     @property
  442.     def client_id(self):
  443.         """The user capabilities
  444.             @rtype: ClientCapabilities"""
  445.         return self._client_id
  446.  
  447.     @rw_property
  448.     def display_name():
  449.         """The display name shown to you contacts
  450.             @type: utf-8 encoded string"""
  451.         def fset(self, display_name):
  452.             if not display_name:
  453.                 return
  454.             self._ns_client.set_display_name(display_name)
  455.         def fget(self):
  456.             return self._display_name
  457.         return locals()
  458.  
  459.     @rw_property
  460.     def presence():
  461.         """The presence displayed to you contacts
  462.             @type: L{Presence<papyon.profile.Presence>}"""
  463.         def fset(self, presence):
  464.             if presence == self._presence:
  465.                 return
  466.             self.__pending_set_presence[0] = presence
  467.             self._ns_client.set_presence(*self.__pending_set_presence)
  468.         def fget(self):
  469.             return self._presence
  470.         return locals()
  471.  
  472.     @rw_property
  473.     def privacy():
  474.         """The default privacy, can be either Privacy.ALLOW or Privacy.BLOCK
  475.             @type: L{Privacy<papyon.profile.Privacy>}"""
  476.         def fset(self, privacy):
  477.             pass #FIXME: set the privacy setting
  478.         def fget(self):
  479.             return self._privacy
  480.         return locals()
  481.  
  482.     @rw_property
  483.     def personal_message():
  484.         """The personal message displayed to you contacts
  485.             @type: utf-8 encoded string"""
  486.         def fset(self, personal_message):
  487.             if personal_message == self._personal_message:
  488.                 return
  489.             self.__pending_set_personal_message[0] = personal_message
  490.             self._ns_client.set_personal_message(*self.__pending_set_personal_message)
  491.         def fget(self):
  492.             return self._personal_message
  493.         return locals()
  494.  
  495.     @rw_property
  496.     def current_media():
  497.         """The current media displayed to you contacts
  498.             @type: (artist: string, track: string)"""
  499.         def fset(self, current_media):
  500.             if current_media == self._current_media:
  501.                 return
  502.             self.__pending_set_personal_message[1] = current_media
  503.             self._ns_client.set_personal_message(*self.__pending_set_personal_message)
  504.         def fget(self):
  505.             return self._current_media
  506.         return locals()
  507.  
  508.     @rw_property
  509.     def signature_sound():
  510.         """The sound played when you are connecting
  511.             @type: string"""
  512.         def fset(self, signature_sound):
  513.             if signature_sound == self._signature_sound:
  514.                 return
  515.             self.__pending_set_personal_message[2] = signature_sound
  516.             self._ns_client.set_personal_message(*self.__pending_set_personal_message)
  517.         def fget(self):
  518.             return self._signature_sound
  519.         return locals()
  520.  
  521.     @rw_property
  522.     def end_point_name():
  523.         def fset(self, name):
  524.             if name == self._end_point_name:
  525.                 return
  526.             self._ns_client.set_end_point_name(name)
  527.         def fget(self):
  528.             return self._end_point_name
  529.         return locals()
  530.  
  531.     @rw_property
  532.     def msn_object():
  533.         """The MSNObject attached to your contact, this MSNObject represents the
  534.         display picture to be shown to your peers
  535.             @type: L{MSNObject<papyon.p2p.MSNObject>}"""
  536.         def fset(self, msn_object):
  537.             if msn_object == self._msn_object:
  538.                 return
  539.             self.__pending_set_presence[2] = msn_object
  540.             self._ns_client.set_presence(*self.__pending_set_presence)
  541.         def fget(self):
  542.             return self._msn_object
  543.         return locals()
  544.  
  545.     @rw_property
  546.     def presence_msn_object():
  547.         def fset(self, args):
  548.             presence, msn_object = args
  549.             if presence == self._presence and msn_object == self._msn_object:
  550.                 return
  551.             self.__pending_set_presence[0] = presence
  552.             self.__pending_set_presence[2] = msn_object
  553.             self._ns_client.set_presence(*self.__pending_set_presence)
  554.         def fget(self):
  555.             return self._presence, self._msn_object
  556.         return locals()
  557.  
  558.     @rw_property
  559.     def personal_message_current_media():
  560.         def fset(self, args):
  561.             personal_message, current_media = args
  562.             if personal_message == self._personal_message and \
  563.                     current_media == self._current_media:
  564.                 return
  565.             self.__pending_set_personal_message[0] = personal_message
  566.             self.__pending_set_personal_message[1] = current_media
  567.             self._ns_client.set_personal_message(*self.__pending_set_personal_message)
  568.         def fget(self):
  569.             return self._personal_message, self._current_media
  570.         return locals()
  571.  
  572.     def request_profile_url(self, callback):
  573.         self._ns_client.send_url_request(('PROFILE', '0x0409'), callback)
  574.  
  575.     def _client_capability_changed(self, client, name, value):
  576.         self.__pending_set_presence[1] = self._client_id
  577.         self._ns_client.set_presence(*self.__pending_set_presence)
  578.  
  579.     def _server_property_changed(self, name, value):
  580.         attr_name = "_" + name.lower().replace("-", "_")
  581.         if attr_name == "_msn_object" and value is not None:
  582.             value = self.__pending_set_presence[2]
  583.         old_value = getattr(self, attr_name)
  584.         if value != old_value:
  585.             setattr(self, attr_name, value)
  586.             self.notify(name)
  587.  
  588.     def do_get_property(self, pspec):
  589.         name = pspec.name.lower().replace("-", "_")
  590.         return getattr(self, name)
  591. gobject.type_register(Profile)
  592.  
  593.  
  594. class Contact(gobject.GObject):
  595.     """Contact related information
  596.         @undocumented: __gsignals__, __gproperties__, do_get_property"""
  597.  
  598.     __gsignals__ =  {
  599.             "infos-changed": (gobject.SIGNAL_RUN_FIRST,
  600.                 gobject.TYPE_NONE,
  601.                 (object,)),
  602.             }
  603.  
  604.     __gproperties__ = {
  605.             "memberships": (gobject.TYPE_UINT,
  606.                 "Memberships",
  607.                 "Membership relation with the contact.",
  608.                 0, 15, 0, gobject.PARAM_READABLE),
  609.  
  610.             "display-name": (gobject.TYPE_STRING,
  611.                 "Friendly name",
  612.                 "A nickname that the user chooses to display to others",
  613.                 "",
  614.                 gobject.PARAM_READWRITE),
  615.  
  616.             "personal-message": (gobject.TYPE_STRING,
  617.                 "Personal message",
  618.                 "The personal message that the user wants to display",
  619.                 "",
  620.                 gobject.PARAM_READABLE),
  621.  
  622.             "current-media": (gobject.TYPE_PYOBJECT,
  623.                 "Current media",
  624.                 "The current media that the user wants to display",
  625.                 gobject.PARAM_READABLE),
  626.  
  627.             "signature-sound": (gobject.TYPE_PYOBJECT,
  628.                 "Signature sound",
  629.                 "The sound played by others' client when the user connects",
  630.                 gobject.PARAM_READABLE),
  631.  
  632.             "presence": (gobject.TYPE_STRING,
  633.                 "Presence",
  634.                 "The presence to show to others",
  635.                 Presence.OFFLINE,
  636.                 gobject.PARAM_READABLE),
  637.  
  638.              "groups": (gobject.TYPE_PYOBJECT,
  639.                  "Groups",
  640.                  "The groups the contact belongs to",
  641.                  gobject.PARAM_READABLE),
  642.  
  643.             "infos": (gobject.TYPE_PYOBJECT,
  644.                 "Informations",
  645.                 "The contact informations",
  646.                 gobject.PARAM_READABLE),
  647.  
  648.             "contact-type": (gobject.TYPE_PYOBJECT,
  649.                 "Contact type",
  650.                 "The contact automatic update status flag",
  651.                  gobject.PARAM_READABLE),
  652.  
  653.             "client-capabilities": (gobject.TYPE_STRING,
  654.                 "Client capabilities",
  655.                 "The client capabilities of the contact 's client",
  656.                 "",
  657.                 gobject.PARAM_READABLE),
  658.  
  659.             "msn-object": (gobject.TYPE_STRING,
  660.                 "MSN Object",
  661.                 "MSN Object attached to the contact, this generally represent "
  662.                 "its display picture",
  663.                 "",
  664.                 gobject.PARAM_READABLE),
  665.             }
  666.  
  667.     def __init__(self, id, network_id, account, display_name, cid=None,
  668.             memberships=Membership.NONE, contact_type=ContactType.REGULAR):
  669.         """Initializer"""
  670.         gobject.GObject.__init__(self)
  671.         self._id = id or "00000000-0000-0000-0000-000000000000"
  672.         self._cid = cid or "00000000-0000-0000-0000-000000000000"
  673.         self._network_id = network_id
  674.         self._account = account
  675.  
  676.         self._display_name = display_name
  677.         self._presence = Presence.OFFLINE
  678.         self._personal_message = ""
  679.         self._current_media = None
  680.         self._signature_sound = None
  681.         self._groups = set()
  682.  
  683.         self._memberships = memberships
  684.         self._contact_type = contact_type
  685.         self._client_capabilities = ClientCapabilities()
  686.         self._msn_object = None
  687.         self._infos = {}
  688.         self._attributes = {'icon_url' : None}
  689.  
  690.     def __repr__(self):
  691.         def memberships_str():
  692.             m = []
  693.             memberships = self._memberships
  694.             if memberships & Membership.FORWARD:
  695.                 m.append('FORWARD')
  696.             if memberships & Membership.ALLOW:
  697.                 m.append('ALLOW')
  698.             if memberships & Membership.BLOCK:
  699.                 m.append('BLOCK')
  700.             if memberships & Membership.REVERSE:
  701.                 m.append('REVERSE')
  702.             if memberships & Membership.PENDING:
  703.                 m.append('PENDING')
  704.             return " | ".join(m)
  705.         template = "<papyon.Contact id='%s' network='%u' account='%s' memberships='%s'>"
  706.         return template % (self._id, self._network_id, self._account, memberships_str())
  707.  
  708.     @property
  709.     def id(self):
  710.         """Contact identifier in a GUID form
  711.             @rtype: GUID string"""
  712.         return self._id
  713.  
  714.     @property
  715.     def attributes(self):
  716.         """Contact attributes
  717.             @rtype: {key: string => value: string}"""
  718.         return self._attributes.copy()
  719.  
  720.     @property
  721.     def cid(self):
  722.         """Contact ID
  723.             @rtype: GUID string"""
  724.         return self._cid
  725.  
  726.     @property
  727.     def network_id(self):
  728.         """Contact network ID
  729.             @rtype: L{NetworkID<papyon.profile.NetworkID>}"""
  730.         return self._network_id
  731.  
  732.     @property
  733.     def account(self):
  734.         """Contact account
  735.             @rtype: utf-8 encoded string"""
  736.         return self._account
  737.  
  738.     @property
  739.     def presence(self):
  740.         """Contact presence
  741.             @rtype: L{Presence<papyon.profile.Presence>}"""
  742.         return self._presence
  743.  
  744.     @property
  745.     def display_name(self):
  746.         """Contact display name
  747.             @rtype: utf-8 encoded string"""
  748.         return self._display_name
  749.  
  750.     @property
  751.     def personal_message(self):
  752.         """Contact personal message
  753.             @rtype: utf-8 encoded string"""
  754.         return self._personal_message
  755.  
  756.     @property
  757.     def current_media(self):
  758.         """Contact current media
  759.             @rtype: (artist: string, track: string)"""
  760.         return self._current_media
  761.  
  762.     @property
  763.     def signature_sound():
  764.         """Contact signature sound
  765.             @type: string"""
  766.         return self._signature_sound
  767.  
  768.     @property
  769.     def groups(self):
  770.         """Contact list of groups
  771.             @rtype: set(L{Group<papyon.profile.Group>}...)"""
  772.         return self._groups
  773.  
  774.     @property
  775.     def infos(self):
  776.         """Contact informations
  777.             @rtype: {key: string => value: string}"""
  778.         return self._infos
  779.  
  780.     @property
  781.     def memberships(self):
  782.         """Contact membership value
  783.             @rtype: bitmask of L{Membership<papyon.profile.Membership>}s"""
  784.         return self._memberships
  785.  
  786.     @property
  787.     def contact_type(self):
  788.         """Contact automatic update status flag
  789.             @rtype: L{ContactType<papyon.profile.ContactType>}"""
  790.         return self._contact_type
  791.  
  792.     @property
  793.     def client_capabilities(self):
  794.         """Contact client capabilities
  795.             @rtype: L{ClientCapabilities}"""
  796.         return self._client_capabilities
  797.  
  798.     @property
  799.     def msn_object(self):
  800.         """Contact MSN Object
  801.             @type: L{MSNObject<papyon.p2p.MSNObject>}"""
  802.         return self._msn_object
  803.  
  804.     @property
  805.     def domain(self):
  806.         """Contact domain, which is basically the part after @ in the account
  807.             @rtype: utf-8 encoded string"""
  808.         result = self._account.split('@', 1)
  809.         if len(result) > 1:
  810.             return result[1]
  811.         else:
  812.             return ""
  813.             
  814.     @property
  815.     def profile_url(self):
  816.         """Contact profile url
  817.             @rtype: string"""
  818.         account = self._account
  819.         return "http://members.msn.com/default.msnw?mem=%s&pgmarket=" % account
  820.  
  821.     ### membership management
  822.     def is_member(self, memberships):
  823.         """Determines if this contact belongs to the specified memberships
  824.             @type memberships: bitmask of L{Membership<papyon.profile.Membership>}s"""
  825.         return (self.memberships & memberships) == memberships
  826.  
  827.     def is_mail_contact(self):
  828.         """Determines if this contact is a mail contact"""
  829.         blank_id = "00000000-0000-0000-0000-000000000000"
  830.         return (not self.is_member(Membership.FORWARD) and self.id != blank_id)
  831.  
  832.     def _set_memberships(self, memberships):
  833.         self._memberships = memberships
  834.         self.notify("memberships")
  835.  
  836.     def _add_membership(self, membership):
  837.         self._memberships |= membership
  838.         self.notify("memberships")
  839.  
  840.     def _remove_membership(self, membership):
  841.         """removes the given membership from the contact
  842.  
  843.             @param membership: the membership to remove
  844.             @type membership: int L{Membership}"""
  845.         self._memberships ^= membership
  846.         self.notify("memberships")
  847.  
  848.     def _server_property_changed(self, name, value): #FIXME, should not be used for memberships
  849.         if name == "client-capabilities":
  850.             value = ClientCapabilities(client_id=value)
  851.         attr_name = "_" + name.lower().replace("-", "_")
  852.         old_value = getattr(self, attr_name)
  853.         if value != old_value:
  854.             setattr(self, attr_name, value)
  855.             self.notify(name)
  856.  
  857.     def _server_attribute_changed(self, name, value):
  858.         self._attributes[name] = value
  859.  
  860.     def _server_infos_changed(self, updated_infos):
  861.         self._infos.update(updated_infos)
  862.         self.emit("infos-changed", updated_infos)
  863.         self.notify("infos")
  864.  
  865.     def _reset(self):
  866.         self._id = "00000000-0000-0000-0000-000000000000"
  867.         self._cid = "00000000-0000-0000-0000-000000000000"
  868.         self._groups = set()
  869.  
  870.         self._server_property_changed("presence", Presence.OFFLINE)
  871.         self._server_property_changed("display-name", self._account)
  872.         self._server_property_changed("personal-message", "")
  873.         self._server_property_changed("current-media", None)
  874.         self._server_property_changed("msn-object", None)
  875.         self._server_property_changed("client-capabilities", "0:0")
  876.         self._server_infos_changed({})
  877.  
  878.  
  879.     ### group management
  880.     def _add_group_ownership(self, group):
  881.         self._groups.add(group)
  882.  
  883.     def _delete_group_ownership(self, group):
  884.         self._groups.discard(group)
  885.  
  886.     def do_get_property(self, pspec):
  887.         name = pspec.name.lower().replace("-", "_")
  888.         return getattr(self, name)
  889. gobject.type_register(Contact)
  890.  
  891.  
  892. class Group(gobject.GObject):
  893.     """Group
  894.         @undocumented: __gsignals__, __gproperties__, do_get_property"""
  895.  
  896.     __gproperties__ = {
  897.         "name": (gobject.TYPE_STRING,
  898.                  "Group name",
  899.                  "Name that the user chooses for the group",
  900.                  "",
  901.                  gobject.PARAM_READABLE)
  902.         }
  903.  
  904.     def __init__(self, id, name):
  905.         """Initializer"""
  906.         gobject.GObject.__init__(self)
  907.         self._id = id
  908.         self._name = name
  909.  
  910.     @property
  911.     def id(self):
  912.         """Group identifier in a GUID form
  913.             @rtype: GUID string"""
  914.         return self._id
  915.  
  916.     @property
  917.     def name(self):
  918.         """Group name
  919.             @rtype: utf-8 encoded string"""
  920.         return self._name
  921.  
  922.     def _server_property_changed(self, name, value):
  923.         attr_name = "_" + name.lower().replace("-", "_")
  924.         old_value = getattr(self, attr_name)
  925.         if value != old_value:
  926.             setattr(self, attr_name, value)
  927.             self.notify(name)
  928.  
  929.     def do_get_property(self, pspec):
  930.         name = pspec.name.lower().replace("-", "_")
  931.         return getattr(self, name)
  932. gobject.type_register(Group)
  933.